/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package dmcn.bl.genetics.execute;

import dmcn.bl.genetics.fitnessfunction.AssignTerminalFitnessFunction;
import dmcn.bl.genetics.fitnessfunction.Evaluator;
import dmcn.bl.genetics.gui.GuiController;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Random;
import org.jgap.*;
import org.jgap.impl.CrossoverOperator;
import org.jgap.impl.DefaultConfiguration;
import org.jgap.impl.IntegerGene;
import org.jgap.impl.MutationOperator;

/**
 *
 * @author Home
 */
public class ExecuteGenetic {

    private int numOfConcetrators;
    private int numOfTerminals;
    private int[][] cost;
    private int[] fortio;
    private int[] xwritikothta;
    private GuiController guiController;

    public void executeGenetic(//Input input,
            int numOfConcetrators,
            int numOfTerminals,
            int[][] exeCost,
            int[] exeFortio,
            int[] exeXwritikothta,
            int populationSize,
            int numOfEvolves,
            boolean moreOptions,
            int mutation,
            double crossOver,
            double approximationOffset,
            int maxApproximationCount,
            int algorithm) {
        try {
            this.numOfConcetrators = numOfConcetrators;
            this.numOfTerminals = numOfTerminals;
            this.cost = exeCost;
            this.fortio = exeFortio;
            this.xwritikothta = exeXwritikothta;
            long startTime = System.currentTimeMillis();
            Configuration conf = new DefaultConfiguration() {

                FitnessEvaluator evaluator = new DefaultFitnessEvaluator() {

                    @Override
                    public boolean isFitter(double a_fitness_value1, double a_fitness_value2) {
                        return a_fitness_value2 > a_fitness_value1;
                    }
                };

                @Override
                public FitnessEvaluator getFitnessEvaluator() {
                    return evaluator;
                }
            };
            if (moreOptions) {
                if (crossOver == 0) {
                    conf.addGeneticOperator(new CrossoverOperator(conf));//, crossOver));
                } else {
                    conf.addGeneticOperator(new CrossoverOperator(conf, crossOver));
                }
                if (mutation == 0) {
                    conf.addGeneticOperator(new MutationOperator(conf));//, mutation));
                } else {
                    conf.addGeneticOperator(new MutationOperator(conf, mutation));
                }
            }
            conf.setPreservFittestIndividual(true);
            conf.setKeepPopulationSizeConstant(true);
            AssignTerminalFitnessFunction ff = new AssignTerminalFitnessFunction(numOfConcetrators, numOfTerminals, exeCost, exeFortio, exeXwritikothta);
            conf.setFitnessFunction(ff);
            //edw bazeis 0,1
            Gene[] sampleGenes = new Gene[numOfConcetrators * numOfTerminals];
            for (int i = 0; i < numOfConcetrators * numOfTerminals; i++) {
                sampleGenes[i] = new IntegerGene(conf, 0, 1);
            }
            IChromosome sampleChromosome = new Chromosome(conf, sampleGenes);
            conf.setSampleChromosome(sampleChromosome);
            conf.setPopulationSize(populationSize);

            guiController = new GuiController(numOfConcetrators, numOfTerminals, exeCost, exeFortio, exeXwritikothta);

            Genotype genotype;
            if (algorithm == 1) {//greedy
                Population population = new Population(conf, getGreedyChromosomeSet(conf, populationSize));
                genotype = new Genotype(conf, population);
            } else if (algorithm == 2) {//no random
                Population population = new Population(conf, getIterativeChromosomeSet(conf, populationSize, false));
                genotype = new Genotype(conf, population);
            } else if (algorithm == 3) {//random
                Population population = new Population(conf, getIterativeChromosomeSet(conf, populationSize, true));
                genotype = new Genotype(conf, population);
            } else if (algorithm == 4) {//simple solution generator
                Population population = new Population(conf, getSimpleSolutionGeneratorSet(conf, populationSize));
                genotype = new Genotype(conf, population);
            } else {//none
                genotype = Genotype.randomInitialGenotype(conf);
            }
            IChromosome bestSolutionSoFar;

//        for (int i = 0; i < numOfEvolves; i++) {
//            population.evolve();
//
//        }

            boolean evolve = true;
            double previousFitness;
            double currentFitness;
            int approximationCount = 0;
            genotype.evolve();
            while (evolve) {
                previousFitness = genotype.getFittestChromosome().getFitnessValue();
                genotype.evolve();
                currentFitness = genotype.getFittestChromosome().getFitnessValue();
                if (previousFitness - currentFitness < approximationOffset && currentFitness != Integer.MAX_VALUE) {
                    approximationCount++;
                } else {
                    approximationCount = 0;
                }
                if (approximationCount > maxApproximationCount) {
                    evolve = false;
                }
                guiController.setBestSolutionSoFar(genotype.getFittestChromosome());
                guiController.paint();
            }
            bestSolutionSoFar = genotype.getFittestChromosome();

            /*
             * print solution to log
             */

            System.out.println("The best solution has a fitness value of "
                    + bestSolutionSoFar.getFitnessValue());

            System.out.println("The best solution is: ");
            System.out.print("   ");
            for (int i = 0; i < numOfTerminals; i++) {
                System.out.print((i + 1) + " ");
            }
            System.out.println("");
            int j = 0;
            for (int i = 0; i < bestSolutionSoFar.getGenes().length; i++) {
                if ((i) % numOfTerminals == 0) {
                    System.out.print(++j + ": ");
                }
                System.out.print(bestSolutionSoFar.getGenes()[i].getAllele() + " ");
                if ((i + 1) % numOfTerminals == 0) {
                    System.out.println("");

                }

            }
            long endTime = System.currentTimeMillis();
            guiController.setGeneticTime((endTime - startTime) / 1000);
            guiController.paint();
            System.out.println("execute at : " + (endTime - startTime) / 1000 + " seconds");
            Configuration.reset();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private IChromosome[] getGreedyChromosomeSet(Configuration conf, int popsize) throws Exception {
        long initTime = System.currentTimeMillis();
        Evaluator evaluator = new Evaluator(numOfConcetrators, numOfTerminals, cost, fortio, xwritikothta);
        IChromosome[] chromosomes = new IChromosome[popsize];
        //first chromosome
        Chromosome chromosomeFirst = new Chromosome(conf);
        //calculate threshold
        int sumFortioTemp = 0;
        int sumXwritikothtaTemp = 0;
        for (int i = 0; i < numOfConcetrators; i++) {
            sumXwritikothtaTemp = sumXwritikothtaTemp + xwritikothta[i];
        }
        for (int i = 0; i < numOfTerminals; i++) {
            sumFortioTemp = sumFortioTemp + fortio[i];
        }
        int diff = sumXwritikothtaTemp - sumFortioTemp;
        int threshold = diff / numOfConcetrators - 1;
        do {
            //initialize genes array
            Gene[] genesFirst = new Gene[numOfConcetrators * numOfTerminals];
            for (int i = 0; i < genesFirst.length; i++) {
                genesFirst[i] = new IntegerGene(conf, 0, 1);
                genesFirst[i].setAllele(0);
            }
            //set values to genes
            int j = 0;
            System.out.println("threshold: " + threshold);
            int sumFortio = threshold;
            for (int i = 0; i < numOfConcetrators; i++) {
                while (j < numOfTerminals) {
                    if (xwritikothta[i] - sumFortio > fortio[j]) {
                        sumFortio = sumFortio + fortio[j];
                        //add
                        genesFirst[i * numOfTerminals + j].setAllele(1);
                        j++;
                    } else {
                        break;
                    }
                }

                sumFortio = threshold;
            }
            //threshold might lead to invalid chromosomes so we make it smaller until a valid solution occurs
            threshold--;
            //set genes to 1st chromosome
            chromosomeFirst.setGenes(genesFirst);
        } while (evaluator.evaluate(chromosomeFirst) == null);

        System.out.println("greedy chromosome 0:");
        guiController.log(chromosomeFirst);

        chromosomes[0] = chromosomeFirst;

        //find rest chromosomes
        Random random = new Random();
        for (int i = 1; i < chromosomes.length; i++) {
            //select terminal to replace
            chromosomes[i] = new Chromosome(conf);
            Gene[] genes = chromosomes[i - 1].getGenes();
            int terminalIndex = random.nextInt(numOfTerminals);
            int[] concetrators = new int[numOfConcetrators];
            //disconnect
            for (int k = 0; k < numOfConcetrators; k++) {
                concetrators[k] = k * numOfTerminals + terminalIndex;
                genes[concetrators[k]].setAllele(0);
            }
            int concetratorIndex = 0;
            //select randomly which concetrator to connect to
//            int z = 0;
            do {
                genes[concetrators[concetratorIndex]].setAllele(0);
                concetratorIndex = random.nextInt(numOfConcetrators);
                genes[concetrators[concetratorIndex]].setAllele(1);
                chromosomes[i].setGenes(genes);
//                System.out.println("trying... " + z++);
            } while (evaluator.evaluate(chromosomes[i]) == null);
            System.out.println("greedy chromosome " + i + ":");
            guiController.log(chromosomes[i]);

        }


        long finalTime = (System.currentTimeMillis() - initTime) / 1000;
        guiController.setInitSetTime(finalTime);
        System.out.println("Initial chromosome set in: " + finalTime + " sec");

        return chromosomes;
    }

    private IChromosome[] getSimpleSolutionGeneratorSet(Configuration conf, int popsize) throws Exception {
        long initTime = System.currentTimeMillis();
        IChromosome[] chromosomes = new IChromosome[popsize];

        for (int z = 0; z < chromosomes.length; z++) {


            //chromosome
            Chromosome chromosome = new Chromosome(conf);
            //initialize genes array
            Gene[] genesFirst = new Gene[numOfConcetrators * numOfTerminals];
            for (int i = 0; i < genesFirst.length; i++) {
                genesFirst[i] = new IntegerGene(conf, 0, 1);
                genesFirst[i].setAllele(0);
            }
            //set values to genes
            int j = z;
//            int j = z - ((z / numOfTerminals) * ((int) popsize / numOfTerminals));
            int sumFortio = 0;
            boolean resetedJ = false;
            for (int i = 0; i < numOfConcetrators; i++) {
                while (xwritikothta[i] - sumFortio > fortio[j]) {
                    sumFortio = sumFortio + fortio[j];
                    //add
                    genesFirst[i * numOfTerminals + j].setAllele(1);
                    j++;
                    if (j == numOfTerminals) {
                        if (!resetedJ) {
                            j = 0;
                            resetedJ = true;
                        } else {
                            break;
                        }
                    }
                    if (j == z) {
                        i = numOfConcetrators;
                        break;
                    }
                }
                sumFortio = 0;
            }
            //set genes to 1st chromosome
            chromosome.setGenes(genesFirst);
            System.out.println("simple chromosome " + z + ":");
            guiController.log(chromosome);

            chromosomes[z] = chromosome;

        }

        long finalTime = (System.currentTimeMillis() - initTime) / 1000;
        guiController.setInitSetTime(finalTime);
        System.out.println("Initial chromosome set in: " + finalTime + " sec");

        return chromosomes;
    }

    private IChromosome[] getIterativeChromosomeSet(Configuration conf, int popsize, boolean randomize) throws Exception {
        long initTime = System.currentTimeMillis();
        IChromosome[] chromosomes = new IChromosome[popsize];
        int z = 0;
        Evaluator evaluator = new Evaluator(numOfConcetrators, numOfTerminals, cost, fortio, xwritikothta);
        //find max
        int[] bits = new int[numOfConcetrators * numOfTerminals];
        for (int i = 0; i < bits.length; i++) {
            bits[i] = 1;
        }
        String binary = Arrays.toString(bits).replace("[", "").replace("]", "").replace(", ", "");
//        Integer max = Integer.parseInt(binary, 2);
        BigInteger max = new BigInteger(binary, 2);
        //find max numofterminals bits

        int[] bitsT = new int[numOfTerminals];
        for (int i = 0; i < bitsT.length; i++) {
            bitsT[i] = 1;
        }
        String binaryT = Arrays.toString(bitsT).replace("[", "").replace("]", "").replace(", ", "");
//        Integer max = Integer.parseInt(binary, 2);
        BigInteger maxT = new BigInteger(binaryT, 2);
        BigInteger maxSteps = max.divide(maxT);
        //iterate
        System.out.println("possible solutions: " + max);
        System.out.println("searching...");
        BigInteger i = BigInteger.valueOf(0);
        BigInteger count = BigInteger.valueOf(1);

        Random random = new Random();

        while (/*i.compareTo(max) == -1 && */z < popsize) {//gia ka8e pi8anh lush-xromosoma
            Chromosome chromosome = new Chromosome(conf);

            if (randomize) {//randomize
//                int numOfBits = random.nextInt(maxT.bitLength());
                BigInteger mult = new BigInteger(maxT.bitLength(), random);
                i = mult.multiply(maxT);
//                System.out.println(i);
            } else {
                i = i.add(maxT);
            }

            String s = i.toString(2);
            while (s.length() < bits.length) {
                s = "0" + s;
            }
            char[] genesCharArray = s.toCharArray();
            Gene[] genes = new Gene[numOfConcetrators * numOfTerminals];//genes
            for (int j = 0; j < genesCharArray.length; j++) {//gia ka8e pi8ano gene gemise to array me genes
                char c = genesCharArray[j];
                Gene gene = new IntegerGene(conf, 0, 1);
                gene.setAllele(Integer.parseInt(new String(new char[]{c})));
                genes[j] = gene;
            }
            chromosome.setGenes(genes);
            if (evaluator.evaluate(chromosome) != null) {
                chromosomes[z] = chromosome;
                z++;
                System.out.println("solution number: " + z);
            }
//            if (count.remainder(BigInteger.valueOf(100000)).equals(BigInteger.valueOf(0))) {
//                System.out.println("search step: " + count + " of " + maxSteps + ", valid solutions found: " + z);
//            }
//            i = i.add(maxT);
            count = count.add(BigInteger.valueOf(1));
        }
        long finalTime = (System.currentTimeMillis() - initTime) / 1000;
        guiController.setInitSetTime(finalTime);
        System.out.println("Initial chromosome set in: " + finalTime + " sec");
        return chromosomes;
    }
}
